/** * Copyright (c) 2012, Lindsay Bradford and other Contributors. * All rights reserved. * * This program and the accompanying materials are made available * under the terms of the BSD 3-Clause licence which accompanies * this distribution, and is available at * http://opensource.org/licenses/BSD-3-Clause */ package blacksmyth.personalfinancier.view; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.text.DateFormat; import java.text.DecimalFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.GregorianCalendar; import javax.swing.DefaultCellEditor; import javax.swing.DefaultListCellRenderer; import javax.swing.InputVerifier; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JFormattedTextField; import javax.swing.JTabbedPane; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.SwingUtilities; import javax.swing.border.LineBorder; import javax.swing.border.TitledBorder; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.ListSelectionEvent; import javax.swing.table.DefaultTableCellRenderer; import blacksmyth.general.BlacksmythSwingUtilities; import blacksmyth.general.FontIconProvider; import blacksmyth.personalfinancier.model.ModelPreferences; /** * A library of methods to construct low-level Swing JComponet widgets in a uniform * way throughout the application based on user preferences in {@link ModelPreferences}. * @author linds * */ public final class WidgetFactory { public static final String ACCOUNT_BUFFER = " "; public static final String PERCENT_FORMAT_PATTERN = "###,##0.00 %"; public static final DecimalFormat PERCENT_FORMAT = new DecimalFormat(PERCENT_FORMAT_PATTERN); public static final String DECIMAL_FORMAT_PATTERN = "#,###,##0.00"; public static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat(DECIMAL_FORMAT_PATTERN); public static final String DATE_FORMAT_PATTERN = "dd/MM/yyyy"; public static final DateFormat DATE_FORMAT = new SimpleDateFormat(DATE_FORMAT_PATTERN); @SuppressWarnings("serial") public static DefaultTableCellRenderer createTableCellRenderer(final int alignment) { return new DefaultTableCellRenderer() { public void setValue(Object value) { this.setHorizontalAlignment(alignment); super.setValue(value); } }; } @SuppressWarnings("serial") public static DefaultTableCellRenderer createAmountCellRenderer() { return createDecimalCellRenderer(JTextField.RIGHT); } @SuppressWarnings("serial") public static DefaultTableCellRenderer createDecimalCellRenderer(final int alignment) { return new DefaultTableCellRenderer() { public void setValue(Object value) { this.setHorizontalAlignment(alignment); this.setText((value == null) ? "" : DECIMAL_FORMAT.format(value)); } }; } public static DefaultTableCellRenderer createDateCellRenderer() { return new DefaultTableCellRenderer() { public void setValue(Object value) { if (value.getClass().equals(GregorianCalendar.class)) { value = ((GregorianCalendar) value).getTime(); } this.setHorizontalAlignment(JTextField.CENTER); this.setText((value == null) ? "" : DATE_FORMAT.format(value)); } }; } public static DefaultCellEditor createAmountCellEditor() { return new DefaultCellEditor( createAmountTextField() ); } public static DefaultCellEditor createDateCellEditor() { return new DefaultCellEditor(createDateTextField()) { @Override public Component getTableCellEditorComponent( JTable table, Object value,boolean isSelected,int row, int column ) { if (value.getClass() == GregorianCalendar.class) { GregorianCalendar valueAsCalendar = (GregorianCalendar) value; String formattedvalue = DATE_FORMAT.format(valueAsCalendar.getTime()); delegate.setValue(formattedvalue); return editorComponent; } return super.getTableCellEditorComponent(table, value, isSelected, row, column); } @Override public Object getCellEditorValue() { String value = (String) delegate.getCellEditorValue(); GregorianCalendar valueAsCalendar; try { valueAsCalendar = new GregorianCalendar(); valueAsCalendar.setTime( DATE_FORMAT.parse(value) ); } catch (ParseException e) { return super.getCellEditorValue(); } return valueAsCalendar; } }; } /** * prepares a <tt>JTable</tt> cell renderer for display based on * various colour preferences in {@link ModelPreferences}. * @param cellRenderer * @param row * @param column * @param isEditable Indicates wheether the cell is editable or not. */ public static void prepareTableCellRenderer(JTable table, Component cellRenderer, int row, int column) { Color rowColor = (row % 2 == 0) ? ViewPreferences.getInstance().getPreferredEvenRowColor() : ViewPreferences.getInstance().getPreferredOddRowColor(); cellRenderer.setBackground(rowColor); if (table.isCellEditable(row,column)) { cellRenderer.setForeground( ViewPreferences.getInstance().getPreferredEditableCellColor() ); } else { cellRenderer.setForeground( ViewPreferences.getInstance().getPreferredUnEditableCellColor() ); } if (table.isCellSelected(row, column)) { cellRenderer.setBackground( ViewPreferences.getInstance().getPreferredSelectedCellColor() ); cellRenderer.setForeground( cellRenderer.getForeground().brighter() ); } } /** * Create a {@link JFormattedTextField} configured to edit decimal numbers in an application-specific way. * @return */ public static JFormattedTextField createCentredDecimalTextField() { return createDecimalTextField(JTextField.CENTER); } /** * Create a {@link JFormattedTextField} configured to edit decimal numbers in an application-specific way. * @return */ public static JFormattedTextField createAmountTextField() { return createDecimalTextField(JTextField.RIGHT); } /** * Create a {@link JFormattedTextField} configured to edit decimal numbers in an application-specific way. * @return */ public static JFormattedTextField createPercentTextField() { return createPercentTextField(JTextField.RIGHT); } /** * Create a {@link JFormattedTextField} configured to edit decimal numbers in an application-specific way. * @return */ public static JFormattedTextField createDecimalTextField(int alignment) { JFormattedTextField field = new JFormattedTextField(DECIMAL_FORMAT); configureGenericTextFieldBehaviour( field, alignment, new Dimension( BlacksmythSwingUtilities.getTextWidth(DECIMAL_FORMAT_PATTERN), (int) field.getPreferredSize().getHeight() ), "0123456789.," ); return field; } /** * Create a {@link JFormattedTextField} configured to edit decimal numbers in an application-specific way. * @return */ public static JFormattedTextField createPercentTextField(int alignment) { JFormattedTextField field = new JFormattedTextField(PERCENT_FORMAT); configureGenericTextFieldBehaviour( field, alignment, new Dimension( BlacksmythSwingUtilities.getTextWidth(PERCENT_FORMAT_PATTERN), (int) field.getPreferredSize().getHeight() ), "0123456789.,%" ); return field; } /** * Create a {@link JFormattedTextField} configured to edit dates in an application-specific way. * @return */ public static JFormattedTextField createDateTextField() { final JFormattedTextField field = new JFormattedTextField(DATE_FORMAT); configureGenericTextFieldBehaviour( field, JTextField.CENTER, new Dimension( BlacksmythSwingUtilities.getTextWidth(DATE_FORMAT_PATTERN), (int) field.getPreferredSize().getHeight() ), "0123456789/" ); return field; } private static void configureGenericTextFieldBehaviour(final JFormattedTextField field, final int alignment, final Dimension preferredSize, final String allowedCharacters) { field.setPreferredSize(preferredSize); field.setInputVerifier( new FormatVerifier() ); field.setForeground( ViewPreferences.getInstance().getPreferredEditableCellColor() ); field.setHorizontalAlignment(alignment); field.addKeyListener(new KeyAdapter() { public void keyTyped(KeyEvent e) { char c = e.getKeyChar(); if (allowedCharacters.indexOf(c) == -1) { e.consume(); // ignore event } } }); field.addFocusListener(new FocusAdapter() { @Override public void focusGained(FocusEvent arg0) { SwingUtilities.invokeLater( new Runnable() { @Override public void run() { field.selectAll(); } }); } @Override public void focusLost(FocusEvent arg0) { SwingUtilities.invokeLater( new Runnable() { @Override public void run() { try { field.commitEdit(); field.postActionEvent(); } catch (ParseException e) { field.requestFocus(); } } }); } }); } /** * Creates a <tt>JComboBox</tt> suitable for application table cell editors with * a static enumeration. * @return JComboBox */ public static JComboBox<String> createTableComboBox() { JComboBox<String> comboBox = new JComboBox<String>(); DefaultListCellRenderer dlcr = new DefaultListCellRenderer(); dlcr.setHorizontalAlignment(DefaultListCellRenderer.CENTER); dlcr.setForeground( ViewPreferences.getInstance().getPreferredEditableCellColor() ); comboBox.setRenderer(dlcr); return comboBox; } /** * Creates a standard {@link TitledBorder} of the specified title and color. * @param title * @param color * @return */ public static TitledBorder createColoredTitledBorder(String title, Color color) { TitledBorder border = new TitledBorder(title); border.setBorder(new LineBorder(color)); border.setTitleColor(color); return border; } public static JTabbedPane createGraphTablePane(JComponent graphComponent, JComponent tableComponent) { JTabbedPane pane = new JTabbedPane(); pane.setTabPlacement(JTabbedPane.LEFT); int currTabIndex = 0; pane.addTab("", tableComponent); pane.setToolTipTextAt(currTabIndex, " View as table "); FontIconProvider.getInstance().setGlyphAsTitle( pane, currTabIndex, FontIconProvider.fa_table ); currTabIndex++; pane.addTab("",graphComponent); pane.setToolTipTextAt(currTabIndex, " View as graph "); FontIconProvider.getInstance().setGlyphAsTitle( pane, currTabIndex, FontIconProvider.fa_pie_chart ); enableSelectionHilightedTabPane(pane); return pane; } public static void enableSelectionHilightedTabPane(JTabbedPane pane) { final Color selectedColor = Color.green.darker().darker().darker(); if (pane.getTabCount() > 0) { pane.setBackgroundAt(0, selectedColor); } pane.addChangeListener( new ChangeListener() { public void stateChanged(ChangeEvent changeEvent) { JTabbedPane sourcePane = (JTabbedPane) changeEvent.getSource(); int index = sourcePane.getSelectedIndex(); for (int tabIndex = 0; tabIndex < sourcePane.getTabCount(); tabIndex++) { sourcePane.setBackgroundAt(tabIndex, sourcePane.getBackground()); } sourcePane.setBackgroundAt(index, selectedColor); } } ); } /** * Creates a JButton that is enabled only when a single row of the specified * JTable is selected. * @param table * @return */ public static JButton createOneSelectedtRowEnabledButton(JTable table) { JTableListeningButton theButton = new JTableListeningButton(table) { public void valueChanged(ListSelectionEvent event) { if (this.selectedTableRows() != 1) { this.setEnabled(false); } else { this.setEnabled(true); } } }; return theButton; } /** * Creates a JButton that is enabled when one or more row of the specified * JTable is selected. * @param table * @return */ public static JButton createMultiSelectedtRowEnabledButton(JTable table) { JTableListeningButton theButton = new JTableListeningButton(table) { public void valueChanged(ListSelectionEvent event) { if (this.selectedTableRows() == 0) { this.setEnabled(false); } else { this.setEnabled(true); } } }; return theButton; } } class FormatVerifier extends InputVerifier { @Override public boolean verify(JComponent input) { try { JFormattedTextField ftf = (JFormattedTextField) input; String text = ftf.getText(); ftf.getFormatter().stringToValue(text); return true; } catch (Exception e) { return false; } } }